﻿using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using XEvolve.Core.Runtime;
using XEvolve.Presentation.Extensions;

namespace XEvolve.Presentation.Controls.ListViewAdvisors
{
    public class DragAndSelection : IDependencyObjectAdvisor
    {
        private object selectedItem;
        private bool isDragging;
        private Point? mouseDownPosition;
        private ListView target;

        public void Initialize(DependencyObject element)
        {
            target = (ListView)element;
            target.AddHandler(UIElement.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler((sender, args) => OnPreviewMouseLeftButtonDown(args)));
            target.AddHandler(UIElement.PreviewMouseLeftButtonUpEvent, new MouseButtonEventHandler((sender, args) => OnPreviewMouseLeftButtonUp(args)));
            target.AddHandler(UIElement.PreviewMouseMoveEvent, new MouseEventHandler((sender, args) => OnPreviewMouseMove(args)));
        }

        protected virtual void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            var position = e.GetPosition(target);
            if (ShouldSetMouseDownPosition(position))
            {
                mouseDownPosition = position;
            }
            var inputElement = (DependencyObject)target.InputHitTest(position);
            var item = inputElement != null ? ((FrameworkElement)inputElement).DataContext : null;
            if (!IsShiftPressed)
            {
                target.SetAnchorItem(item);
            }
            if (inputElement == null || (IsLink(inputElement)) || (inputElement.FindAncestor<CheckBox>() != null)) return;
            if (item == null || IsControlPressed || IsShiftPressed || !target.SelectedItems.Contains(item)) return;
            selectedItem = item;
            e.Handled = true;
        }

        protected virtual void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            if (!isDragging && selectedItem != null)
            {
                target.SelectedItems.Clear();
                target.SelectedItems.Add(selectedItem);
                var element = ((UIElement)target.ItemContainerGenerator.ContainerFromItem(selectedItem));
                if (element != null)
                    element.Focus();
            }
            mouseDownPosition = null;
            isDragging = false;
            selectedItem = null;
        }

        protected virtual void OnPreviewMouseMove(MouseEventArgs e)
        {
            if ((e.LeftButton != MouseButtonState.Pressed) || (!mouseDownPosition.HasValue) ||
                ((e.GetPosition(target) - mouseDownPosition.Value).Length <= 2)) return;
            isDragging = true;
            try
            {
                var dragSource = target is IDragSource ? (IDragSource) target : target.GetAdapter<IDragSource>();
                var dragData = dragSource != null ? dragSource.GetDragAndDropData() : null;
                if (dragData != null)
                {
                    DragDrop.DoDragDrop(target, dragData, DragDropEffects.Move);
                }
            }
            catch (COMException)
            {
            }
            finally
            {
                mouseDownPosition = null;
                isDragging = false;
            }
        }

        private bool ShouldSetMouseDownPosition(Point position)
        {
            return !HitScrollBarOrHeader(position) && ItemHit(typeof(ListViewItem), position);
        }

        private bool HitScrollBarOrHeader(Point position)
        {
            return ItemHit(typeof(ScrollBar), position) || ItemHit(typeof(GridViewColumnHeader), position);
        }

        private bool ItemHit(Type type, Point position)
        {
            var hittenContainer = (DependencyObject)target.InputHitTest(position);
            while (!(hittenContainer == null || hittenContainer is ListView))
            {
                if (type.IsAssignableFrom(hittenContainer.GetType())) return true;
                hittenContainer = VisualTreeHelper.GetParent(hittenContainer);
            }
            return false;
        }

        private static bool IsLink(DependencyObject inputElement)
        {
            var commandSource = (inputElement is ICommandSource)
                                    ? (ICommandSource) inputElement
                                    : inputElement.GetAdapter<ICommandSource>();
            var command = commandSource != null ? commandSource.Command : null;
            return inputElement is TextBlock && command != null;
        }

        private static bool IsControlPressed
        {
            get { return (Keyboard.Modifiers == ModifierKeys.Control); }
        }

        private static bool IsShiftPressed
        {
            get { return (Keyboard.Modifiers == ModifierKeys.Shift); }
        }
    }    
}
